#ifndef __LOG_ADAPTER_H__
#define __LOG_ADAPTER_H__

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <map>
#include <string>
#include <memory>
#include <functional>

#include "common_utils.h"

#define CTX {__FILE__, __FUNCTION__, __LINE__}

#define LOG_DEBUG(fmt, ...) this->log(CTX, LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...)  this->log(CTX, LOG_LEVEL_INFO,  fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...)  this->log(CTX, LOG_LEVEL_WARN,  fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) this->log(CTX, LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)

typedef std::function<void(const std::string&)> log_func;

enum {
	LOG_LEVEL_DEBUG = 1,
	LOG_LEVEL_INFO  = 2,
	LOG_LEVEL_WARN  = 3,
	LOG_LEVEL_ERROR = 4,
};

enum {
	LOG_TAG_FILE = 0x1,
	LOG_TAG_FUNC = 0x2,
	LOG_TAG_LINE = 0x4,
	LOG_TAG_MODULE = 0X8,
};

struct LogContext
{
	const char* file;
	const char* func;
	int line;
};

class LogDefaulter
{
public:
	static void set_logger(log_func func) {
		_func = func;
	}

	static log_func get_logger() {
		return _func;
	}

    static void set_log_level(int level) {
        _log_level = level; 
    }

    static int get_log_level() {
        return _log_level; 
    }

private:
	static log_func _func;
    static int      _log_level;
};

template <class T>
class LogAdapter
{
public:
	LogAdapter() {
		_open_log = false;
		_func = nullptr;
	//	_class_name = format_class_name();

		_class_name = CommonUtils::get_class_name<T>();

		_log_tags |= (LOG_TAG_FILE | LOG_TAG_FUNC | LOG_TAG_LINE | LOG_TAG_MODULE);
        _log_level = 0;
	}

	void open_log(bool open = true) {
		_open_log = open;
	}

	void set_log_tags(int tags) {
		_log_tags = tags;
	}

	void set_log_level(int level) {
		_log_level = level;
	}

	void set_logger(log_func func) {
		_func = func;
	}

	template <class... Args>
	void log(LogContext ctx, int level, const std::string& fmt, Args... args) {
		auto logger = (_func != nullptr ? _func : LogDefaulter::get_logger());
		if (!logger) {
			return ;
		}

        auto log_level = (_log_level != 0 ? _log_level : LogDefaulter::get_log_level());
        if (level < log_level) {
            return ; 
        }

		std::string tags;
        std::map<int, std::pair<std::string, std::string>> tag_values;

		get_tag_values(ctx, tag_values);
		for (auto& item : tag_values) {
			if (item.first & _log_tags) {
				tags += CommonUtils::format_string(
					"%s:%s,", 
					item.second.first.c_str(),
					item.second.second.c_str()
				);
			}
		}
		if (tags.length() > 0) {
			tags = tags.substr(0, tags.length() - 1);
		}

		auto message = CommonUtils::format_string(fmt, args...);
		auto log_message = CommonUtils::format_string(
			"%s [%s] %s", 
			CommonUtils::date_ms().c_str(), 
			tags.c_str(),
			message.c_str()
		);
		logger(log_message);
	}

protected:
	virtual void get_tag_values(
		const LogContext& ctx, 
		std::map<int, std::pair<std::string, std::string>>& tag_values
    )
	{
        tag_values[LOG_TAG_FILE] = std::make_pair("file", ctx.file);
        tag_values[LOG_TAG_FUNC] = std::make_pair("func", ctx.func);
        tag_values[LOG_TAG_LINE] = std::make_pair("line", std::to_string(ctx.line));
        tag_values[LOG_TAG_FILE] = std::make_pair("module", _class_name);
	}
/*
private:
    std::string format_class_name() {
        std::string class_name = typeid(T).name(); 
        const char*p = class_name.c_str();
        while (*p != 0 && *p >= '0' && *p <= '9') {
            p++;
        }
        return p;
    }
*/
protected:
	log_func _func;

	bool _open_log;

	int  _log_tags;
	int  _log_level;

	std::string _class_name;
};

#endif

